# -*- encoding: utf-8 -*-

"""
------------------------------------------
@File       : life_game_v1.py
@Author     : maixiaochai
@Email      : maixiaochai@outlook.com
@CreatedOn  : 2021/7/1 14:09
------------------------------------------
> 康威生命游戏(Conway's Game of Life)，经典的有限状态自动机
> 初始条件：
    1）在任意长宽的二维网格中，每个单元格都必须处于 ALIVE（有生命存在）或 EMPTY（没有生物或者原有生物已经死亡）状态
    2）时钟每走一个，游戏就要前进一步

> 游戏规则：
    1）如果单元格里有生命体，并且周围的生命体数<2,那么，该单元格内的生命体死亡
    2）如果单元格里有生命体，并且周围的生命体数>3,那么，该单元格内的生命体死亡
    3）如果单元格为空，并且周围的生命体数=3，那么，该单元格内的生命体复活

> 这里以 5 X 5的网格前进4步为例
"""

ALIVE = "*"
EMPTY = "-"


class Grid:
    """
        管理单元格的状态
        get/set中，如果坐标越界，那么会自动绕回，产生一种无限折返的效果
    """

    def __init__(self, height, width):
        self.height = height
        self.width = width
        self.rows = []

        for _ in range(self.height):
            self.rows.append([EMPTY] * self.width)

    def get(self, y, x):
        """获取指定位置的状态值"""
        return self.rows[y % self.height][x % self.width]

    def set(self, y, x, state):
        """设置指定位置的状态值"""
        self.rows[y % self.height][x % self.width] = state

    def __str__(self):
        """返回表格的字符串图"""
        return "\n".join(["".join(row) for row in self.rows])


def count_neighbors(y, x, get):
    """
        辅助函数，计算相邻单元格的状态
        计算顺序：从北方开始，逆时针转一圈
    """
    n_ = get(y - 1, x + 0)  # North
    ne = get(y - 1, x + 1)  # Northeast
    e_ = get(y + 0, x + 1)  # East
    se = get(y + 1, x + 1)  # Southeast
    s_ = get(y + 1, x + 0)  # South
    sw = get(y + 1, x - 1)  # Southwest
    w_ = get(y + 0, x - 1)  # West
    nw = get(y - 1, x - 1)  # Northwest

    neighbor_states = [n_, ne, e_, se, s_, sw, w_, nw]

    count = 0
    for state in neighbor_states:
        if state == ALIVE:
            count += 1
    return count


def game_logic(state, neighbors):
    """ 游戏逻辑"""
    if state == ALIVE:
        if neighbors < 2:
            return EMPTY  # Die: too few

        elif neighbors > 3:  # Die: too many
            return EMPTY

    else:
        if neighbors == 3:
            return ALIVE  # Regenerate

    return state


def step_cell(y, x, get, set):
    """ 根据坐标，获取目前生命状态，计算下一步的生命状态，并赋值"""
    old_state = get(y, x)
    neighbors = count_neighbors(y, x, get)
    new_state = game_logic(old_state, neighbors)
    set(y, x, new_state)


def simulate(grid):
    """
        模拟生命变化，模拟一整代
        simulate: 模拟，仿真
    """
    next_grid = Grid(grid.height, grid.width)

    for y in range(grid.height):
        for x in range(grid.width):
            step_cell(y, x, grid.get, next_grid.set)

    return next_grid


class ColumnPrinter:
    def __init__(self):
        self.columns = []

    def append(self, data):
        self.columns.append(data)

    def __str__(self):
        row_count = 1
        for data in self.columns:
            row_count = max(
                # len(data.splitlines()) + 1 是因为顶行添加了一行代序号
                row_count, len(data.splitlines()) + 1
            )

        # 这里大概的思路是：一行一行地完成，目前也只能是这种思路，但是，逻辑上可以有其它可能
        rows = [''] * row_count
        for j in range(row_count):
            for i, data in enumerate(self.columns):
                line = data.splitlines()[max(0, j - 1)]
                if j == 0:
                    padding = ' ' * (len(line) // 2)
                    rows[j] += padding + str(i) + padding
                else:
                    rows[j] += line

                if (i + 1) < len(self.columns):
                    rows[j] += ' | '

        return '\n'.join(rows)


def demo(grid_cls):
    grid = grid_cls(5, 9)
    grid.set(0, 3, ALIVE)
    grid.set(1, 4, ALIVE)
    grid.set(2, 2, ALIVE)
    grid.set(2, 3, ALIVE)
    grid.set(2, 4, ALIVE)

    # print(grid)
    """
    out:
        ---*-----
        ----*----
        --***----
        ---------
        ---------
    """

    columns = ColumnPrinter()

    for i in range(5):
        columns.append(str(grid))
        grid = simulate(grid)

    print(columns)
    """
    out:
            0     |     1     |     2     |     3     |     4    
        ---*----- | --------- | --------- | --------- | ---------
        ----*---- | --*-*---- | ----*---- | ---*----- | ----*----
        --***---- | ---**---- | --*-*---- | ----**--- | -----*---
        --------- | ---*----- | ---**---- | ---**---- | ---***---
        --------- | --------- | --------- | --------- | ---------
    """


if __name__ == '__main__':
    demo(Grid)
